package com.umeox.moto.watch.themes.drawable;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.util.AttributeSet;

import com.umeox.moto.watch.themes.R;
import com.umeox.moto.watch.themes.app.ThemeConfig;
import com.umeox.moto.watch.themes.util.ColorUtils;
import com.umeox.moto.watch.themes.util.MetricsUtils;
import com.umeox.moto.watch.themes.util.ThemeUtils;


public class RadioButtonDrawable extends Drawable implements Animatable {
    private static final float[] TICK_DATA = new float[]{0f, 0.473f, 0.367f, 0.839f, 1f, 0.207f};
    private static final int INNER_TYPE_CIRCLE = 0x00000000;
    private static final int INNER_TYPE_TICK = 0x00000001;

    private boolean mRunning = false;
    private Paint mPaint;
    private long mStartTime;
    private float mAnimProgress;
    private int mAnimDuration;
    private final Runnable mUpdater = this::update;
    private int mStrokeSize;
    private int mWidth;
    private int mHeight;
    private int mRadius;
    private int mInnerType;
    private int mInnerRadius;
    private int mPrevColor;
    private int mCurColor;
    private ColorStateList mStrokeColor;
    private Path mTickPath;
    private float mTickPathProgress = -1f;
    private boolean mChecked = false;
    private boolean mInEditMode = false;
    private boolean mAnimEnable = true;

    private RadioButtonDrawable(int width, int height, int strokeSize, ColorStateList strokeColor, int radius, int innerType, int innerRadius, int animDuration) {
        mAnimDuration = animDuration;
        mStrokeSize = strokeSize;
        mWidth = width;
        mHeight = height;
        mInnerType = innerType;
        mRadius = radius;
        mInnerRadius = innerRadius;
        mStrokeColor = strokeColor;

        mPaint = new Paint();
        mPaint.setAntiAlias(true);

        mTickPath = new Path();
    }

    public void setInEditMode(boolean b) {
        mInEditMode = b;
    }

    public boolean isAnimEnable() {
        return mAnimEnable;
    }

    public void setAnimEnable(boolean b) {
        mAnimEnable = b;
    }

    @Override
    public int getIntrinsicWidth() {
        return mWidth;
    }

    @Override
    public int getIntrinsicHeight() {
        return mHeight;
    }

    @Override
    public int getMinimumWidth() {
        return mWidth;
    }

    @Override
    public int getMinimumHeight() {
        return mHeight;
    }

    @Override
    public boolean isStateful() {
        return true;
    }

    @Override
    public void draw(Canvas canvas) {
        if (mChecked) {
            drawChecked(canvas);
        } else {
            drawUnchecked(canvas);
        }
    }

    private void drawChecked(Canvas canvas) {
        float cx = getBounds().exactCenterX();
        float cy = getBounds().exactCenterY();

        if (isRunning()) {
            float halfStrokeSize = mStrokeSize / 2f;
            float inTime = (mRadius - halfStrokeSize) / (mRadius - halfStrokeSize + mRadius - mStrokeSize - mInnerRadius);

            if (mAnimProgress < inTime) {
                float inProgress = mAnimProgress / inTime;
                float outerRadius = mRadius + halfStrokeSize * (1f - inProgress);
                float innerRadius = (mRadius - halfStrokeSize) * (1f - inProgress);

                mPaint.setColor(ColorUtils.getMiddleColor(mPrevColor, mCurColor, inProgress));
                mPaint.setStrokeWidth(outerRadius - innerRadius);
                mPaint.setStyle(Paint.Style.STROKE);
                canvas.drawCircle(cx, cy, (outerRadius + innerRadius) / 2, mPaint);
            } else {
                float outProgress = (mAnimProgress - inTime) / (1f - inTime);
                if (mInnerType == INNER_TYPE_CIRCLE) {
                    float innerRadius = (mRadius - mStrokeSize) * (1 - outProgress) + mInnerRadius * outProgress;
                    mPaint.setColor(mCurColor);
                    mPaint.setStyle(Paint.Style.FILL);
                    canvas.drawCircle(cx, cy, innerRadius, mPaint);
                } else if (mInnerType == INNER_TYPE_TICK) {
                    mPaint.setStrokeWidth(mStrokeSize);
                    mPaint.setStyle(Paint.Style.STROKE);

                    float x = cx - mInnerRadius;
                    float y = cy - mInnerRadius;
                    canvas.drawPath(getTickPath(mTickPath, x, y, mInnerRadius * 2, outProgress, true), mPaint);
                }

                float outerRadius = mRadius + halfStrokeSize * outProgress;
                mPaint.setStrokeWidth(mStrokeSize);
                mPaint.setStyle(Paint.Style.STROKE);
                canvas.drawCircle(cx, cy, outerRadius - halfStrokeSize, mPaint);
            }
        } else {
            mPaint.setColor(mCurColor);
            mPaint.setStrokeWidth(mStrokeSize);
            mPaint.setStyle(Paint.Style.STROKE);
            canvas.drawCircle(cx, cy, mRadius, mPaint);

            if (mInnerType == INNER_TYPE_CIRCLE) {
                mPaint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(cx, cy, mInnerRadius, mPaint);
            } else if (mInnerType == INNER_TYPE_TICK) {
                float x = cx - mInnerRadius;
                float y = cy - mInnerRadius;
                canvas.drawPath(getTickPath(mTickPath, x, y, mInnerRadius * 2, 1f, true), mPaint);
            }
        }
    }

    private void drawUnchecked(Canvas canvas) {
        float cx = getBounds().exactCenterX();
        float cy = getBounds().exactCenterY();

        if (isRunning()) {
            float halfStrokeSize = mStrokeSize / 2f;
            float inTime = (mRadius - mStrokeSize - mInnerRadius) / (mRadius - halfStrokeSize + mRadius - mStrokeSize - mInnerRadius);

            if (mAnimProgress < inTime) {
                float inProgress = mAnimProgress / inTime;

                if (mInnerType == INNER_TYPE_CIRCLE) {
                    float innerRadius = (mRadius - mStrokeSize) * inProgress + mInnerRadius * (1f - inProgress);

                    mPaint.setColor(ColorUtils.getMiddleColor(mPrevColor, mCurColor, inProgress));
                    mPaint.setStyle(Paint.Style.FILL);
                    canvas.drawCircle(cx, cy, innerRadius, mPaint);
                } else if (mInnerType == INNER_TYPE_TICK) {
                    mPaint.setStrokeWidth(mStrokeSize);
                    mPaint.setStyle(Paint.Style.STROKE);

                    float x = cx - mInnerRadius;
                    float y = cy - mInnerRadius;
                    canvas.drawPath(getTickPath(mTickPath, x, y, mInnerRadius * 2, inProgress, false), mPaint);
                }

                float outerRadius = mRadius + halfStrokeSize * (1f - inProgress);
                mPaint.setStrokeWidth(mStrokeSize);
                mPaint.setStyle(Paint.Style.STROKE);
                canvas.drawCircle(cx, cy, outerRadius - halfStrokeSize, mPaint);
            } else {
                float outProgress = (mAnimProgress - inTime) / (1f - inTime);
                float outerRadius = mRadius + halfStrokeSize * outProgress;
                float innerRadius = (mRadius - halfStrokeSize) * outProgress;

                mPaint.setColor(mCurColor);
                mPaint.setStrokeWidth(outerRadius - innerRadius);
                mPaint.setStyle(Paint.Style.STROKE);
                canvas.drawCircle(cx, cy, (outerRadius + innerRadius) / 2, mPaint);
            }
        } else {
            mPaint.setColor(mCurColor);
            mPaint.setStrokeWidth(mStrokeSize);
            mPaint.setStyle(Paint.Style.STROKE);
            canvas.drawCircle(cx, cy, mRadius, mPaint);
        }
    }

    private Path getTickPath(Path path, float x, float y, float size, float progress, boolean in) {
        if (mTickPathProgress == progress) {
            return path;
        }

        mTickPathProgress = progress;

        float x1 = x + size * TICK_DATA[0];
        float y1 = y + size * TICK_DATA[1];
        float x2 = x + size * TICK_DATA[2];
        float y2 = y + size * TICK_DATA[3];
        float x3 = x + size * TICK_DATA[4];
        float y3 = y + size * TICK_DATA[5];

        float d1 = (float) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
        float d2 = (float) Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
        float midProgress = d1 / (d1 + d2);

        path.reset();

        if (in) {
            path.moveTo(x1, y1);

            if (progress < midProgress) {
                progress = progress / midProgress;
                path.lineTo(x1 * (1 - progress) + x2 * progress, y1 * (1 - progress) + y2 * progress);
            } else {
                progress = (progress - midProgress) / (1f - midProgress);
                path.lineTo(x2, y2);
                path.lineTo(x2 * (1 - progress) + x3 * progress, y2 * (1 - progress) + y3 * progress);
            }
        } else {
            path.moveTo(x3, y3);

            if (progress < midProgress) {
                progress = progress / midProgress;
                path.lineTo(x2, y2);
                path.lineTo(x1 * (1 - progress) + x2 * progress, y1 * (1 - progress) + y2 * progress);
            } else {
                progress = (progress - midProgress) / (1f - midProgress);
                path.lineTo(x2 * (1 - progress) + x3 * progress, y2 * (1 - progress) + y3 * progress);
            }
        }

        return path;
    }

    @Override
    protected boolean onStateChange(int[] state) {
        boolean checked = isChecked(state);
        int color = mStrokeColor.getColorForState(state, mCurColor);
        boolean needRedraw = false;

        if (mChecked != checked) {
            mChecked = checked;
            needRedraw = true;
            if (!mInEditMode && mAnimEnable) {
                start();
            }
        }

        if (mCurColor != color) {
            mPrevColor = isRunning() ? mCurColor : color;
            mCurColor = color;
            needRedraw = true;
        } else if (!isRunning()) {
            mPrevColor = color;
        }

        return needRedraw;
    }

    private boolean isChecked(int[] states) {
        if (states == null) {
            return false;
        }
        for (int temp : states) {
            if (temp == android.R.attr.state_checked) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    //Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    private void resetAnimation() {
        mStartTime = SystemClock.uptimeMillis();
        mAnimProgress = 0f;
    }

    @Override
    public void start() {
        resetAnimation();
        scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ThemeConfig.FRAME_DURATION);
        invalidateSelf();
    }

    @Override
    public void stop() {
        mRunning = false;
        unscheduleSelf(mUpdater);
        invalidateSelf();
    }

    @Override
    public boolean isRunning() {
        return mRunning;
    }

    @Override
    public void scheduleSelf(Runnable what, long when) {
        mRunning = true;
        super.scheduleSelf(what, when);
    }

    private void update() {
        long curTime = SystemClock.uptimeMillis();
        mAnimProgress = Math.min(1f, (float) (curTime - mStartTime) / mAnimDuration);

        if (mAnimProgress == 1f) {
            mRunning = false;
        }

        if (isRunning()) {
            scheduleSelf(mUpdater, SystemClock.uptimeMillis() + ThemeConfig.FRAME_DURATION);
        }

        invalidateSelf();
    }

    public static class Builder {

        private int mAnimDuration = 400;
        private int mStrokeSize = 4;
        private int mWidth = 64;
        private int mHeight = 64;
        private int mRadius = 18;
        private int mInnerType = INNER_TYPE_CIRCLE;
        private int mInnerRadius = 10;
        private ColorStateList mStrokeColor;

        public Builder() {
        }

        public Builder(Context context, int defStyleRes) {
            this(context, null, 0, defStyleRes);
        }

        public Builder(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RadioButtonDrawable, defStyleAttr != 0 ? defStyleAttr : R.attr.u_radioButtonStyle, defStyleRes);

            width(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_width, (int) MetricsUtils.convertDpToPixel(context, 32)));
            height(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_height, (int) MetricsUtils.convertDpToPixel(context, 32)));
            strokeSize(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_strokeSize, (int) MetricsUtils.convertDpToPixel(context, 2)));
            radius(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_radius, (int) MetricsUtils.convertDpToPixel(context, 10)));
            setInnerType(a.getInteger(R.styleable.RadioButtonDrawable_rbd_innerType, 0));
            innerRadius(a.getDimensionPixelSize(R.styleable.RadioButtonDrawable_rbd_innerRadius, (int) MetricsUtils.convertDpToPixel(context, 5)));
            strokeColor(a.getColorStateList(R.styleable.RadioButtonDrawable_rbd_strokeColor));
            animDuration(a.getInt(R.styleable.RadioButtonDrawable_rbd_animDuration, context.getResources().getInteger(android.R.integer.config_mediumAnimTime)));

            a.recycle();

            if (mStrokeColor == null) {
                int[][] states = new int[][]{
                        new int[]{-android.R.attr.state_checked},
                        new int[]{android.R.attr.state_checked},
                };
                int[] colors = new int[]{
                        ThemeUtils.colorControlNormal(context, 0xFF000000),
                        ThemeUtils.colorControlActivated(context, 0xFF000000),
                };
                strokeColor(new ColorStateList(states, colors));
            }
        }

        public RadioButtonDrawable build() {
            if (mStrokeColor == null) {
                mStrokeColor = ColorStateList.valueOf(0xFF000000);
            }

            return new RadioButtonDrawable(mWidth, mHeight, mStrokeSize, mStrokeColor, mRadius, mInnerType, mInnerRadius, mAnimDuration);
        }

        public Builder width(int width) {
            mWidth = width;
            return this;
        }

        public Builder height(int height) {
            mHeight = height;
            return this;
        }

        public Builder strokeSize(int size) {
            mStrokeSize = size;
            return this;
        }

        public Builder strokeColor(int color) {
            mStrokeColor = ColorStateList.valueOf(color);
            return this;
        }

        public Builder strokeColor(ColorStateList color) {
            mStrokeColor = color;
            return this;
        }

        public Builder radius(int radius) {
            mRadius = radius;
            return this;
        }

        public Builder setInnerType(int innerType) {
            if (innerType == 0) {
                mInnerType = INNER_TYPE_CIRCLE;
            } else if (innerType == 1) {
                mInnerType = INNER_TYPE_TICK;
            } else {
                mInnerType = innerType;
            }
            return this;
        }

        public Builder innerRadius(int radius) {
            mInnerRadius = radius;
            return this;
        }

        public Builder animDuration(int duration) {
            mAnimDuration = duration;
            return this;
        }
    }
}
